Protocols

Protocol Syntax

                  protocol SomeProtocol {

    // protocol definition goes here

}
                
                  struct SomeStructure: FirstProtocol, AnotherProtocol {

    // structure definition goes here

}
                
                  class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {

    // class definition goes here

}
                

Property Requirements

                  protocol SomeProtocol {

    var mustBeSettable: Int { get set }

    var doesNotNeedToBeSettable: Int { get }

}
                
                  protocol AnotherProtocol {

    static var someTypeProperty: Int { get set }

}
                
                  protocol FullyNamed {

    var fullName: String { get }

}
                
                  struct Person: FullyNamed {

    var fullName: String

}

let john = Person(fullName: "John Appleseed")

// john.fullName is "John Appleseed"
                
                  class Starship: FullyNamed {

    var prefix: String?

    var name: String

    init(name: String, prefix: String? = nil) {

        self.name = name

        self.prefix = prefix

    }

    var fullName: String {

        return (prefix != nil ? prefix! + " " : "") + name

    }

}

var ncc1701 = Starship(name: "Enterprise", prefix: "USS")

// ncc1701.fullName is "USS Enterprise"
                

Method Requirements

                  protocol SomeProtocol {

    static func someTypeMethod()

}
                
                  protocol RandomNumberGenerator {

    func random() -> Double

}
                
                  class LinearCongruentialGenerator: RandomNumberGenerator {

    var lastRandom = 42.0

    let m = 139968.0

    let a = 3877.0

    let c = 29573.0

    func random() -> Double {

        lastRandom = ((lastRandom * a + c)

            .truncatingRemainder(dividingBy:m))

        return lastRandom / m

    }

}

let generator = LinearCongruentialGenerator()

print("Here's a random number: \(generator.random())")

// Prints "Here's a random number: 0.3746499199817101"

print("And another one: \(generator.random())")

// Prints "And another one: 0.729023776863283"
                

Mutating Method Requirements

                  protocol Togglable {

    mutating func toggle()

}
                
                  enum OnOffSwitch: Togglable {

    case off, on

    mutating func toggle() {

        switch self {

        case .off:

            self = .on

        case .on:

            self = .off

        }

    }

}

var lightSwitch = OnOffSwitch.off

lightSwitch.toggle()

// lightSwitch is now equal to .on
                

Initializer Requirements

                  protocol SomeProtocol {

    init(someParameter: Int)

}
                

Class Implementations of Protocol Initializer Requirements

                  class SomeClass: SomeProtocol {

    required init(someParameter: Int) {

        // initializer implementation goes here

    }

}
                
                  protocol SomeProtocol {

    init()

}



class SomeSuperClass {

    init() {

        // initializer implementation goes here

    }

}



class SomeSubClass: SomeSuperClass, SomeProtocol {

    // "required" from SomeProtocol conformance; "override" from SomeSuperClass

    required override init() {

        // initializer implementation goes here

    }

}
                

Protocols as Types

                  class Dice {

    let sides: Int

    let generator: RandomNumberGenerator

    init(sides: Int, generator: RandomNumberGenerator) {

        self.sides = sides

        self.generator = generator

    }

    func roll() -> Int {

        return Int(generator.random() * Double(sides)) + 1

    }

}
                
                  var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())

for _ in 1...5 {

    print("Random dice roll is \(d6.roll())")

}

// Random dice roll is 3

// Random dice roll is 5

// Random dice roll is 4

// Random dice roll is 5

// Random dice roll is 4
                

Delegation

                  protocol DiceGame {

    var dice: Dice { get }

    func play()

}

protocol DiceGameDelegate: AnyObject {

    func gameDidStart(_ game: DiceGame)

    func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)

    func gameDidEnd(_ game: DiceGame)

}
                
                  class SnakesAndLadders: DiceGame {

    let finalSquare = 25

    let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())

    var square = 0

    var board: [Int]

    init() {

        board = Array(repeating: 0, count: finalSquare + 1)

        board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02

        board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08

    }

    weak var delegate: DiceGameDelegate?

    func play() {

        square = 0

        delegate?.gameDidStart(self)

        gameLoop: while square != finalSquare {

            let diceRoll = dice.roll()

            delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)

            switch square + diceRoll {

            case finalSquare:

                break gameLoop

            case let newSquare where newSquare > finalSquare:

                continue gameLoop

            default:

                square += diceRoll

                square += board[square]

            }

        }

        delegate?.gameDidEnd(self)

    }

}
                
                  class DiceGameTracker: DiceGameDelegate {

    var numberOfTurns = 0

    func gameDidStart(_ game: DiceGame) {

        numberOfTurns = 0

        if game is SnakesAndLadders {

            print("Started a new game of Snakes and Ladders")

        }

        print("The game is using a \(game.dice.sides)-sided dice")

    }

    func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {

        numberOfTurns += 1

        print("Rolled a \(diceRoll)")

    }

    func gameDidEnd(_ game: DiceGame) {

        print("The game lasted for \(numberOfTurns) turns")

    }

}
                
                  let tracker = DiceGameTracker()

let game = SnakesAndLadders()

game.delegate = tracker

game.play()

// Started a new game of Snakes and Ladders

// The game is using a 6-sided dice

// Rolled a 3

// Rolled a 5

// Rolled a 4

// Rolled a 5

// The game lasted for 4 turns
                

Adding Protocol Conformance with an Extension

                  protocol TextRepresentable {

    var textualDescription: String { get }

}
                
                  extension Dice: TextRepresentable {

    var textualDescription: String {

        return "A \(sides)-sided dice"

    }

}
                
                  let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())

print(d12.textualDescription)

// Prints "A 12-sided dice"
                
                  extension SnakesAndLadders: TextRepresentable {

    var textualDescription: String {

        return "A game of Snakes and Ladders with \(finalSquare) squares"

    }

}

print(game.textualDescription)

// Prints "A game of Snakes and Ladders with 25 squares"
                

Conditionally Conforming to a Protocol

                  extension Array: TextRepresentable where Element: TextRepresentable {

    var textualDescription: String {

        let itemsAsText = self.map { $0.textualDescription }

        return "[" + itemsAsText.joined(separator: ", ") + "]"

    }

}

let myDice = [d6, d12]

print(myDice.textualDescription)

// Prints "[A 6-sided dice, A 12-sided dice]"
                

Declaring Protocol Adoption with an Extension

                  struct Hamster {

    var name: String

    var textualDescription: String {

        return "A hamster named \(name)"

    }

}

extension Hamster: TextRepresentable {}
                
                  let simonTheHamster = Hamster(name: "Simon")

let somethingTextRepresentable: TextRepresentable = simonTheHamster

print(somethingTextRepresentable.textualDescription)

// Prints "A hamster named Simon"
                

Adopting a Protocol Using a Synthesized Implementation

                  struct Vector3D: Equatable {

    var x = 0.0, y = 0.0, z = 0.0

}



let twoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)

let anotherTwoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)

if twoThreeFour == anotherTwoThreeFour {

    print("These two vectors are also equivalent.")

}

// Prints "These two vectors are also equivalent."
                
                  enum SkillLevel: Comparable {

    case beginner

    case intermediate

    case expert(stars: Int)

}

var levels = [SkillLevel.intermediate, SkillLevel.beginner,

              SkillLevel.expert(stars: 5), SkillLevel.expert(stars: 3)]

for level in levels.sorted() {

    print(level)

}

// Prints "beginner"

// Prints "intermediate"

// Prints "expert(stars: 3)"

// Prints "expert(stars: 5)"
                

Collections of Protocol Types

                  let things: [TextRepresentable] = [game, d12, simonTheHamster]
                
                  for thing in things {

    print(thing.textualDescription)

}

// A game of Snakes and Ladders with 25 squares

// A 12-sided dice

// A hamster named Simon
                

Protocol Inheritance

                  protocol InheritingProtocol: SomeProtocol, AnotherProtocol {

    // protocol definition goes here

}
                
                  protocol PrettyTextRepresentable: TextRepresentable {

    var prettyTextualDescription: String { get }

}
                
                  extension SnakesAndLadders: PrettyTextRepresentable {

    var prettyTextualDescription: String {

        var output = textualDescription + ":\n"

        for index in 1...finalSquare {

            switch board[index] {

            case let ladder where ladder > 0:

                output += "▲ "

            case let snake where snake < 0:

                output += "▼ "

            default:

                output += "○ "

            }

        }

        return output

    }

}
                
                  print(game.prettyTextualDescription)

// A game of Snakes and Ladders with 25 squares:

// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○
                

Class-Only Protocols

                  protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {

    // class-only protocol definition goes here

}
                

Protocol Composition

                  protocol Named {

    var name: String { get }

}

protocol Aged {

    var age: Int { get }

}

struct Person: Named, Aged {

    var name: String

    var age: Int

}

func wishHappyBirthday(to celebrator: Named & Aged) {

    print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")

}

let birthdayPerson = Person(name: "Malcolm", age: 21)

wishHappyBirthday(to: birthdayPerson)

// Prints "Happy birthday, Malcolm, you're 21!"
                
                  class Location {

    var latitude: Double

    var longitude: Double

    init(latitude: Double, longitude: Double) {

        self.latitude = latitude

        self.longitude = longitude

    }

}

class City: Location, Named {

    var name: String

    init(name: String, latitude: Double, longitude: Double) {

        self.name = name

        super.init(latitude: latitude, longitude: longitude)

    }

}

func beginConcert(in location: Location & Named) {

    print("Hello, \(location.name)!")

}



let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)

beginConcert(in: seattle)

// Prints "Hello, Seattle!"
                

Checking for Protocol Conformance

                  protocol HasArea {

    var area: Double { get }

}
                
                  class Circle: HasArea {

    let pi = 3.1415927

    var radius: Double

    var area: Double { return pi * radius * radius }

    init(radius: Double) { self.radius = radius }

}

class Country: HasArea {

    var area: Double

    init(area: Double) { self.area = area }

}
                
                  class Animal {

    var legs: Int

    init(legs: Int) { self.legs = legs }

}
                
                  let objects: [AnyObject] = [

    Circle(radius: 2.0),

    Country(area: 243_610),

    Animal(legs: 4)

]
                
                  for object in objects {

    if let objectWithArea = object as? HasArea {

        print("Area is \(objectWithArea.area)")

    } else {

        print("Something that doesn't have an area")

    }

}

// Area is 12.5663708

// Area is 243610.0

// Something that doesn't have an area
                

Optional Protocol Requirements

                  @objc protocol CounterDataSource {

    @objc optional func increment(forCount count: Int) -> Int

    @objc optional var fixedIncrement: Int { get }

}
                
                  class Counter {

    var count = 0

    var dataSource: CounterDataSource?

    func increment() {

        if let amount = dataSource?.increment?(forCount: count) {

            count += amount

        } else if let amount = dataSource?.fixedIncrement {

            count += amount

        }

    }

}
                
                  class ThreeSource: NSObject, CounterDataSource {

    let fixedIncrement = 3

}
                
                  var counter = Counter()

counter.dataSource = ThreeSource()

for _ in 1...4 {

    counter.increment()

    print(counter.count)

}

// 3

// 6

// 9

// 12
                
                  class TowardsZeroSource: NSObject, CounterDataSource {

    func increment(forCount count: Int) -> Int {

        if count == 0 {

            return 0

        } else if count < 0 {

            return 1

        } else {

            return -1

        }

    }

}
                
                  counter.count = -4

counter.dataSource = TowardsZeroSource()

for _ in 1...5 {

    counter.increment()

    print(counter.count)

}

// -3

// -2

// -1

// 0

// 0
                

Protocol Extensions

                  extension RandomNumberGenerator {

    func randomBool() -> Bool {

        return random() > 0.5

    }

}
                
                  let generator = LinearCongruentialGenerator()

print("Here's a random number: \(generator.random())")

// Prints "Here's a random number: 0.3746499199817101"

print("And here's a random Boolean: \(generator.randomBool())")

// Prints "And here's a random Boolean: true"
                

Providing Default Implementations

                  extension PrettyTextRepresentable  {

    var prettyTextualDescription: String {

        return textualDescription

    }

}
                

Adding Constraints to Protocol Extensions

                  extension Collection where Element: Equatable {

    func allEqual() -> Bool {

        for element in self {

            if element != self.first {

                return false

            }

        }

        return true

    }

}
                
                  let equalNumbers = [100, 100, 100, 100, 100]

let differentNumbers = [100, 100, 200, 100, 200]
                
                  print(equalNumbers.allEqual())

// Prints "true"

print(differentNumbers.allEqual())

// Prints "false"